package cyclestats

// 周期统计

import (
	"context"
	"errors"
	"github.com/robfig/cron"
	"go.mongodb.org/mongo-driver/bson"
	"kratos-layout/pkg/logger"
	"sort"
	"time"
)

const (
	StatsTimeUnitSecond = time.Second
	StatsTimeUnitMinute = time.Minute
	StatsTimeUnitHour   = time.Hour
)

// StatTypeConf 统计类型配置
type StatTypeConf struct {
	Type     string        // 统计类型
	TimeUnit time.Duration // 时间单位，如StatsTimeUnitMinute
}

// StatExpireConf 统计过期配置，目前只支持所有统计类型拥有相同的过期配置
type StatExpireConf struct {
	TimeUnit time.Duration // 时间单位，如StatsTimeUnitMinute
	Duration time.Duration // 存活时长，如time.Hour*6
}

type StatType struct {
	Type       string
	TimeUnit   time.Duration
	UpdateTime time.Time
	Processed  map[time.Duration]time.Time
}

type CycleStats struct {
	data       *Data
	ExpireConf []StatExpireConf
	Types      map[string]*StatType
}

var CycleStatsInst *CycleStats

// NewCycleStats 创建周期统计实例
// types: 统计类型配置
// expireConf: 统计过期配置，目前只支持所有统计类型拥有相同的过期配置。为nil时，使用默认配置
// dbUri: 数据库连接字符串
func NewCycleStats(types []StatTypeConf, expireConf []StatExpireConf, dbUri string) *CycleStats {
	for _, e := range expireConf {
		if e.TimeUnit != StatsTimeUnitSecond && e.TimeUnit != StatsTimeUnitMinute && e.TimeUnit != StatsTimeUnitHour {
			logger.Errorf("NewCycleStats error: TimeUnit(%v) is invalid", e.TimeUnit)
			return nil
		}

		if e.Duration < time.Minute {
			logger.Errorf("NewCycleStats error: Duration(%v) is invalid", e.Duration)
			return nil
		}
	}

	if expireConf == nil {
		expireConf = []StatExpireConf{
			{TimeUnit: StatsTimeUnitSecond, Duration: time.Hour * 6},         // 60*6个文档，最大60*60*6个统计点
			{TimeUnit: StatsTimeUnitMinute, Duration: time.Hour * 24 * 7},    // 24*7个文档，最大60*24*7个统计点
			{TimeUnit: StatsTimeUnitHour, Duration: time.Hour * 24 * 31 * 6}, // 31*6个文档，最大24*31*6个统计点
		}
	}

	if CycleStatsInst != nil {
		return CycleStatsInst
	}

	data, err := NewData(dbUri)
	if err != nil {
		return nil
	}

	s := &CycleStats{
		data:       data,
		ExpireConf: expireConf,
		Types:      map[string]*StatType{},
	}

	for _, v := range types {
		s.Types[v.Type] = &StatType{
			Type:       v.Type,
			TimeUnit:   v.TimeUnit,
			UpdateTime: time.Time{},
			Processed:  map[time.Duration]time.Time{},
		}
	}

	s.data.CreateIndexes()
	s.expireStats(expireConf)

	c := cron.New()
	// 添加每分钟触发的定时任务
	c.AddFunc("0 * * * * *", func() {
		// 每分钟触发的任务逻辑
		now := time.Now()
		// 举例：
		// 1. 上次更新时间为：01:01:01, 当前时间为：01:01:05, 则不需要更新
		// 2. 上次更新时间为：01:01:01, 当前时间为：01:02:05, 则需要更新
		// 3. 上次更新时间为：01:01:01, 当前时间为：02:01:05, 则需要更新
		for _, v := range s.Types {
			curTimeUnit := v.TimeUnit
			UpperTimeUnit := timeUnitUpper(v.TimeUnit)
			for {
				processed := v.Processed[UpperTimeUnit]
				timestamp := timeTruncate(curTimeUnit, v.UpdateTime)
				if processed != timestamp && timestamp.Add(UpperTimeUnit).Before(now) {
					// 取出当前时间单元的统计数据，添加到上级时间单元
					data, err := s.data.GetCycleStatsByCond(context.Background(),
						bson.M{"type": v.Type, "time_unit": curTimeUnit, "timestamp": timestamp}, 1, 1)
					if err == nil && len(data) > 0 {
						s.AddStats(UpperTimeUnit, timestamp, v.Type, data[0].Total)
					}

					v.Processed[UpperTimeUnit] = timestamp
				}

				if UpperTimeUnit == StatsTimeUnitHour {
					break
				}
				curTimeUnit = UpperTimeUnit
				UpperTimeUnit = timeUnitUpper(curTimeUnit)
			}
		}
	})
	c.Start()

	CycleStatsInst = s
	return CycleStatsInst
}

// AddStats 更新统计数据
// timeUnit: 统计时间单位，可选值：StatsTimeUnitSecond, StatsTimeUnitMinute, StatsTimeUnitHour
// timestamp: 统计时间，格式为：2006-01-02 15:04:05
// statsType: 统计类型，可选值：user, order, product, ...
// value: 统计值
func (s *CycleStats) AddStats(timeUnit time.Duration, timestamp time.Time, statsType string, value int64) error {
	stats := s.Types[statsType]
	if stats == nil {
		logger.Errorf("AddStats error: statsType(%v) is invalid", statsType)
		return errors.New("statsType is invalid")
	}

	err := s.data.AddStats(timeUnit, timestamp, statsType, value)
	if err != nil {
		logger.Errorf("AddStats error: %v", err)
		return err
	}

	stats.UpdateTime = timestamp
	return nil
}

// GetTodayTotalStats 获取今日统计数据
func (s *CycleStats) GetTodayTotalStats(ctx context.Context, statsType string) int64 {
	now := time.Now()
	total := int64(0)
	filter := bson.M{
		"timestamp": now.Truncate(time.Hour * 24),
		"type":      statsType,
		"time_unit": StatsTimeUnitHour,
	}
	stats, err := s.data.GetCycleStatsByCond(ctx, filter, 1, 1)
	if err == nil && len(stats) > 0 {
		total += stats[0].Total
	}

	filter = bson.M{
		"timestamp": now.Truncate(time.Hour),
		"type":      statsType,
		"time_unit": StatsTimeUnitMinute,
	}
	stats, err = s.data.GetCycleStatsByCond(ctx, filter, 1, 1)
	if err == nil && len(stats) > 0 {
		total += stats[0].Total
	}

	filter = bson.M{
		"timestamp": now.Truncate(time.Minute),
		"type":      statsType,
		"time_unit": StatsTimeUnitSecond,
	}
	stats, err = s.data.GetCycleStatsByCond(ctx, filter, 1, 1)
	if err == nil && len(stats) > 0 {
		total += stats[0].Total
	}

	return total
}

// GetNDaysTotalStats 获取近N日统计数据
func (s *CycleStats) GetNDaysTotalStats(ctx context.Context, statsType string, days int) int64 {
	now := time.Now()
	total := int64(0)
	filter := bson.M{
		"timestamp": bson.M{
			"$gte": time.Now().Add(-(time.Duration(days) * time.Hour * 24)),
		},
		"type":      statsType,
		"time_unit": StatsTimeUnitHour,
	}
	stats, err := s.data.GetCycleStatsByCond(ctx, filter, 1, 1024)
	if err == nil && len(stats) > 0 {
		for _, stat := range stats {
			total += stat.Total
		}
	}

	filter = bson.M{
		"timestamp": now.Truncate(time.Hour),
		"type":      statsType,
		"time_unit": StatsTimeUnitMinute,
	}
	stats, err = s.data.GetCycleStatsByCond(ctx, filter, 1, 1)
	if err == nil && len(stats) > 0 {
		total += stats[0].Total
	}

	filter = bson.M{
		"timestamp": now.Truncate(time.Minute),
		"type":      statsType,
		"time_unit": StatsTimeUnitSecond,
	}
	stats, err = s.data.GetCycleStatsByCond(ctx, filter, 1, 1)
	if err == nil && len(stats) > 0 {
		total += stats[0].Total
	}

	return total
}

// StatsTrend 统计趋势
type StatsTrend struct {
	TimeStamp time.Time `json:"timestamp"`
	Count     int64
}

// GetStatsTrend 获取统计趋势，按时间排序
func (s *CycleStats) GetStatsTrend(ctx context.Context, statsType string, timeUnit time.Duration, untilNow time.Duration) []StatsTrend {
	startTime := time.Now().Add(-untilNow)
	filter := bson.M{
		"timestamp": bson.M{
			"$gte": timeTruncate(timeUnit, startTime),
		},
		"type":      statsType,
		"time_unit": timeUnit,
	}

	stats, err := s.data.GetCycleStatsByCond(ctx, filter, 1, 1024)
	if err != nil {
		logger.Errorf("GetStatsTrend error: %v", err)
		return nil
	}

	var trend []StatsTrend
	for _, entity := range stats {
		for k, v := range entity.Values {
			ts := entity.Timestamp.Add(time.Duration(k)*timeUnit + timeUnit)
			if ts.Sub(startTime) >= 0 {
				trend = append(trend, StatsTrend{
					TimeStamp: entity.Timestamp.Add(time.Duration(k) * timeUnit).Local(),
					Count:     v,
				})
			}
		}
	}

	// sort trend by TimeStamp
	sort.Slice(trend, func(i, j int) bool {
		return trend[i].TimeStamp.Before(trend[j].TimeStamp)
	})
	return trend
}

// expireStats 定期删除过期的统计数据
func (s *CycleStats) expireStats(expires []StatExpireConf) {
	for _, e := range expires {
		go func(e StatExpireConf) {
			interval := time.Hour
			if e.TimeUnit == StatsTimeUnitSecond {
				interval = time.Minute
			}
			ticker := time.NewTicker(interval)
			defer ticker.Stop()

			for {
				select {
				case <-ticker.C:
					//	删除已过期的数据
					s.data.DeleteExpiredStats(e.TimeUnit, e.Duration)
				}
			}
		}(e)
	}
}
